#pragma once

#include "Follows.h"

Follows::Follows(VarTable* vt,StmtTable *st,Result *result)
{
	myST = st;
	myVT = vt;
	myResult = result;
};

void Follows::set(INDEX stmtNo1, INDEX stmtNo2) {
	followsTableStmt.insert(std::make_pair<INDEX,INDEX>(stmtNo1,stmtNo2));
	++count;
};

void Follows::computeClosure()
{
	pair <unordered_multimap<INDEX,INDEX>::iterator, unordered_multimap<INDEX,INDEX>::iterator> ret;
	unordered_set<INDEX> accum;
	bool isChanged;
	int initSize;
	for(int i = 0;i<=myST->getSize();i++)
	{
		isChanged = true;
		accum.clear();

		pair <unordered_multimap<INDEX,INDEX>::iterator, unordered_multimap<INDEX,INDEX>::iterator> ret;
		ret = followsTableStmt.equal_range(i);
		for (unordered_multimap<INDEX,INDEX>::iterator it=ret.first; it!=ret.second; ++it)
		{
			accum.insert(it->second);
		}
		while(isChanged)
		{
			isChanged = false;
			initSize = accum.size();
			
			for (auto it = accum.begin(); it != accum.end(); ++it)
			{
				pair <unordered_multimap<INDEX,INDEX>::iterator, unordered_multimap<INDEX,INDEX>::iterator> ret;
				ret = followsTableStmt.equal_range(*it);
				for (unordered_multimap<INDEX,INDEX>::iterator it=ret.first; it!=ret.second; ++it)
				{
					accum.insert(it->second);
				}
			}
			if(initSize!=accum.size())
			{
				isChanged = true;
			}
		}
		for (auto it = accum.begin(); it != accum.end(); ++it)
		{
			followsTableStmtClosure.insert((std::make_pair<INDEX,INDEX>(i,*it)));
		}
		//--------------------------------New------------------------------------
		hash_set<INDEX> newHashSet;
		for (auto it = accum.begin(); it != accum.end(); ++it)
		{
			newHashSet.insert(*it);
		}
		for (auto it = accum.begin(); it != accum.end(); ++it)
		{
			closure.insert((std::make_pair<INDEX,hash_set<INDEX>>(i,newHashSet)));
		}

		//-------------------------------reverse

		for(int i = 0;i<=myST->getSize();i++)
		{
			isChanged = true;
			accum.clear();

			for (auto it = followsTableStmt.begin(); it != followsTableStmt.end(); ++it)
			{
				if (it->second==i)
				{
					accum.insert(it->first);
				}
			}
			while(isChanged)
			{
				isChanged = false;
				initSize = accum.size();
			
				for (auto it = accum.begin(); it != accum.end(); ++it)
				{
					for (auto it2 = followsTableStmt.begin(); it2 != followsTableStmt.end(); ++it2)
					{
						if (it2->second==*it)
						{
							accum.insert(it2->first);
						}
					}
				}
				if(initSize!=accum.size())
				{
					isChanged = true;
				}
			}
			//--------------------------------New------------------------------------
			hash_set<INDEX> newHashSet;
			for (auto it = accum.begin(); it != accum.end(); ++it)
			{
				newHashSet.insert(*it);
			}
			for (auto it = accum.begin(); it != accum.end(); ++it)
			{
				reverseClosure.insert((std::make_pair<INDEX,hash_set<INDEX>>(i,newHashSet)));
			}
		}
	}
};

Schema Follows::getSynSyn(DESIGN_ENTITY de1, NAME de1Name, DESIGN_ENTITY de2, NAME de2Name)
{
	vector<NAME> attNames;
	attNames.push_back(de1Name);
	attNames.push_back(de2Name);
	Schema x(2,attNames);
	if(de1Name!=de2Name)
	{
		for (auto it = followsTableStmt.begin(); it != followsTableStmt.end(); ++it) {
			if ((myST->stmtTable.at(it->first) == de1 || de1 == "stmt") && (myST->stmtTable.at(it->second) == de2 || de2 == "stmt")) {
				vector<INDEX> vect;
				vect.push_back(it->first);
				vect.push_back(it->second);
				x.insertTuple(vect);
			}
		}
		return x;
	}
	else
	{
		return x;
	}
};

Schema Follows::getSynInt(DESIGN_ENTITY de1, NAME de1Name, INDEX para2)
{
	vector<NAME> attNames;
	attNames.push_back(de1Name);
	Schema x(1,attNames);
	for (auto it = followsTableStmt.begin(); it != followsTableStmt.end(); ++it) {
		if ((myST->stmtTable.at(it->first) == de1 || de1 == "stmt") && it->second==para2) {
			vector<INDEX> vect;
			vect.push_back(it->first);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Follows::getSynUnd(DESIGN_ENTITY de1, NAME de1Name)
{
	vector<NAME> attNames;
	attNames.push_back(de1Name);
	Schema x(1,attNames);
	for (auto it = followsTableStmt.begin(); it != followsTableStmt.end(); ++it) {
		if ((myST->stmtTable.at(it->first) == de1 || de1 == "stmt")) {
			vector<INDEX> vect;
			vect.push_back(it->first);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Follows::getIntSyn(INDEX para1, DESIGN_ENTITY de2, NAME de2Name)
{
	vector<NAME> attNames;
	attNames.push_back(de2Name);
	Schema x(1,attNames);
	for (auto it = followsTableStmt.begin(); it != followsTableStmt.end(); ++it) {
		if (it->first==para1 && (myST->stmtTable.at(it->second) == de2 || de2 == "stmt")) {
			vector<INDEX> vect;
			vect.push_back(it->second);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Follows::getIntInt(INDEX para1, INDEX para2)
{
	vector<NAME> attNames;
	attNames.push_back("_");
	Schema x(1,attNames);
	for (auto it = followsTableStmt.begin(); it != followsTableStmt.end(); ++it) {
		if ((it->first==para1) && (it->second==para2)) {
			vector<INDEX> vect;
			vect.push_back(para1);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Follows::getIntUnd(INDEX para1)
{
	vector<NAME> attNames;
	attNames.push_back("_");
	Schema x(1,attNames);
	for (auto it = followsTableStmt.begin(); it != followsTableStmt.end(); ++it) {
		if (it->first==para1) {
			vector<INDEX> vect;
			vect.push_back(it->second);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Follows::getUndInt(INDEX para2)
{
	vector<NAME> attNames;
	attNames.push_back("_");
	Schema x(1,attNames);
	for (auto it = followsTableStmt.begin(); it != followsTableStmt.end(); ++it) {
		if ((it->second==para2)) {
			vector<INDEX> vect;
			vect.push_back(para2);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Follows::getUndSyn(DESIGN_ENTITY de2, NAME de2Name)
{
	vector<NAME> attNames;
	attNames.push_back(de2Name);
	Schema x(1,attNames);
	for (auto it = followsTableStmt.begin(); it != followsTableStmt.end(); ++it) {
		if (myST->stmtTable.at(it->second) == de2 || de2 == "stmt") {
			vector<INDEX> vect;
			vect.push_back(it->second);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Follows::getUndUnd()
{
	vector<NAME> attNames;
	attNames.push_back("_");
	Schema x(1,attNames);
	for (auto it = followsTableStmt.begin(); it != followsTableStmt.end(); ++it) 
	{
		vector<INDEX> vect;
		vect.push_back(it->second);
		x.insertTuple(vect);
	}
	return x;
};

Schema Follows::getSynSynClosure(DESIGN_ENTITY de1, NAME de1Name, DESIGN_ENTITY de2, NAME de2Name)
{
	vector<NAME> attNames;
	attNames.push_back(de1Name);
	attNames.push_back(de2Name);
	Schema x(2,attNames);
	if(de1Name!=de2Name)
	{
		for (auto it = followsTableStmtClosure.begin(); it != followsTableStmtClosure.end(); ++it) {
			if ((myST->stmtTable.at(it->first) == de1 || de1 == "stmt") && (myST->stmtTable.at(it->second) == de2 || de2 == "stmt")) {
				vector<INDEX> vect;
				vect.push_back(it->first);
				vect.push_back(it->second);
				x.insertTuple(vect);
			}
		}
		//---------------------------------------------------------------------------------------------------------------
		if((!myResult->bags.at(de1Name).empty())&&(!myResult->bags.at(de2Name).empty()))
		{
			for (auto it = myResult->bags.at(de1Name).begin(); it != myResult->bags.at(de1Name).end(); ++it)
			{
				 std::hash_multimap<INDEX,hash_set<INDEX>>::const_iterator it2;
				 it2 = closure.find(*it);
				 if(!it2->second.empty())
				 {
					 for (auto it3 = myResult->bags.at(de2Name).begin(); it3 != myResult->bags.at(de2Name).end(); ++it3)
					 {
						 std::hash_set<INDEX>::const_iterator otherIter;
						 otherIter = it2->second.find(*it3);
						 if(otherIter == it2->second.end())
						 {
							 myResult->bags.at(de2Name).erase(*it3);
						 }
					 }
				 }
				 else
				 {
					 myResult->bags.at(de1Name).erase(*it);
				 }
			}
		}
		else if((myResult->bags.at(de1Name).empty())&&(!myResult->bags.at(de2Name).empty()))
		{
			for (auto it = myResult->bags.at(de2Name).begin(); it != myResult->bags.at(de2Name).end(); ++it)
			{
				 std::hash_multimap<INDEX,hash_set<INDEX>>::const_iterator it2;
				 it2 = reverseClosure.find(*it);
				 if(!it2->second.empty())
				 {
					 for (auto it3 = it2->second.begin(); it3 != it2->second.end(); ++it3)
					 {
						 if(de1=="stmt"||myST->stmtTable.at(*it3)==de1)
						 {
							myResult->bags.at(de1Name).insert(*it3);
						 }
					 }
				 }
				 else
				 {
					 myResult->bags.at(de2Name).erase(*it);
				 }
			}
		}
		else if((!myResult->bags.at(de1Name).empty())&&(myResult->bags.at(de2Name).empty()))
		{
			for (auto it = myResult->bags.at(de1Name).begin(); it != myResult->bags.at(de1Name).end(); ++it)
			{
				 std::hash_multimap<INDEX,hash_set<INDEX>>::const_iterator it2;
				 it2 = closure.find(*it);
				 if(!it2->second.empty())
				 {
					 for (auto it3 = it2->second.begin(); it3 != it2->second.end(); ++it3)
					 {
						 if(de2=="stmt"||myST->stmtTable.at(*it3)==de2)
						 {
							myResult->bags.at(de2Name).insert(*it3);
						 }
					 }
				 }
				 else
				 {
					 myResult->bags.at(de1Name).erase(*it);
				 }
			}
		}
		else
		{
			for (auto it = closure.begin(); it != closure.end(); ++it)
			{
				if(!it->second.empty()&&(de1=="stmt"||myST->stmtTable.at(it->first)==de1))
				{
					myResult->bags.at(de1Name).insert(it->first);
					for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2)
					{
						if(de2=="stmt"||myST->stmtTable.at(*it2)==de2)
						{
							myResult->bags.at(de2Name).insert(*it2);
						}
					}
				}
			}
		}
		//---------------------------------------------------------------------------------------------------------------

		return x;
	}
	else
	{
		return x;
	}
};

Schema Follows::getSynIntClosure(DESIGN_ENTITY de1, NAME de1Name, INDEX para2)
{
	vector<NAME> attNames;
	attNames.push_back(de1Name);
	Schema x(1,attNames);
	for (auto it = followsTableStmtClosure.begin(); it != followsTableStmtClosure.end(); ++it) {
		if ((myST->stmtTable.at(it->first) == de1 || de1 == "stmt") && it->second==para2) {
			vector<INDEX> vect;
			vect.push_back(it->first);
			x.insertTuple(vect);
		}
	}

	//---------------------------------------------------------------------------------------------------------------
		if((!myResult->bags.at(de1Name).empty()))
		{
			for (auto it = myResult->bags.at(de1Name).begin(); it != myResult->bags.at(de1Name).end(); ++it)
			{
				 std::hash_multimap<INDEX,hash_set<INDEX>>::const_iterator it2;
				 it2 = closure.find(*it);
				 
				 std::hash_set<INDEX>::const_iterator otherIter;
				 otherIter = it2->second.find(para2);
				 if(otherIter == it2->second.end())
				 {
					 myResult->bags.at(de1Name).erase(*it);
				 }
			}
		}
		else
		{
			for (auto it = closure.begin(); it != closure.end(); ++it)
			{
				if(!it->second.empty()&&(de1=="stmt"||myST->stmtTable.at(it->first)==de1))
				{
					myResult->bags.at(de1Name).insert(it->first);
					
					std::hash_set<INDEX>::const_iterator otherIter;
					otherIter = it->second.find(para2);
					if(otherIter != it->second.end())
					{
						myResult->bags.at(de1Name).insert(it->first);
					}
				}
			}
		}
		//---------------------------------------------------------------------------------------------------------------
	return x;
};

Schema Follows::getSynUndClosure(DESIGN_ENTITY de1, NAME de1Name)
{
	vector<NAME> attNames;
	attNames.push_back(de1Name);
	Schema x(1,attNames);
	for (auto it = followsTableStmtClosure.begin(); it != followsTableStmtClosure.end(); ++it) {
		if ((myST->stmtTable.at(it->first) == de1 || de1 == "stmt")) {
			vector<INDEX> vect;
			vect.push_back(it->first);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Follows::getIntSynClosure(INDEX para1, DESIGN_ENTITY de2, NAME de2Name)
{
	vector<NAME> attNames;
	attNames.push_back(de2Name);
	Schema x(1,attNames);
	for (auto it = followsTableStmtClosure.begin(); it != followsTableStmtClosure.end(); ++it) {
		if (it->first==para1 && (myST->stmtTable.at(it->second) == de2 || de2 == "stmt")) {
			vector<INDEX> vect;
			vect.push_back(it->second);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Follows::getIntIntClosure(INDEX para1, INDEX para2)
{
	vector<NAME> attNames;
	attNames.push_back("_");
	Schema x(1,attNames);
	for (auto it = followsTableStmtClosure.begin(); it != followsTableStmtClosure.end(); ++it) {
		if ((it->first==para1) && (it->second==para2)) {
			vector<INDEX> vect;
			vect.push_back(para1);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Follows::getIntUndClosure(INDEX para1)
{
	vector<NAME> attNames;
	attNames.push_back("_");
	Schema x(1,attNames);
	for (auto it = followsTableStmtClosure.begin(); it != followsTableStmtClosure.end(); ++it) {
		if (it->first==para1) {
			vector<INDEX> vect;
			vect.push_back(it->second);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Follows::getUndIntClosure(INDEX para2)
{
	vector<NAME> attNames;
	attNames.push_back("_");
	Schema x(1,attNames);
	for (auto it = followsTableStmtClosure.begin(); it != followsTableStmtClosure.end(); ++it) {
		if ((it->second==para2)) {
			vector<INDEX> vect;
			vect.push_back(para2);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Follows::getUndSynClosure(DESIGN_ENTITY de2, NAME de2Name)
{
	vector<NAME> attNames;
	attNames.push_back(de2Name);
	Schema x(1,attNames);
	for (auto it = followsTableStmtClosure.begin(); it != followsTableStmtClosure.end(); ++it) {
		if (myST->stmtTable.at(it->second) == de2 || de2 == "stmt") {
			vector<INDEX> vect;
			vect.push_back(it->second);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Follows::getUndUndClosure()
{
	vector<NAME> attNames;
	attNames.push_back("_");
	Schema x(1,attNames);
	for (auto it = followsTableStmtClosure.begin(); it != followsTableStmtClosure.end(); ++it) 
	{
		vector<INDEX> vect;
		vect.push_back(it->second);
		x.insertTuple(vect);
	}
	return x;
};

void Follows::printAll() {
	cout << "Printing Parent Table" << endl;
	for (auto it = followsTableStmt.begin(); it != followsTableStmt.end(); ++it) {
		cout << it->first << " : " << it->second << endl;
	}
};